//-----------------------------------------------------------------------------
//
// @file	AnimatedCurves.cpp
// @brief	AnimatedCurves functionality
// @author	Vinod Melapudi
// @date	01May2011
//
//-----------------------------------------------------------------------------


#include <stdlib.h>
#include <iostream>

#include "Renderer.h"
#include "Application.h"
#include "Image.h"
#include "BezierCurve.h"

#include "AnimatedCurves.h"

using namespace std;

// renderer initialized in main file
extern Renderer *g_renderer;



//
// @brief	Main entry
//
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	AppConfig config;
	config.appName = "Animated Curves";
	config.cmdShow = nCmdShow;
	config.width = 1200;
	config.height = 700;
	config.hInstance = hInstance;
	
	AnimatedCurves animCurves;
	config.scene = &animCurves;

	return Application::GetInstance()->Process( config );
}


AnimatedCurves::AnimatedCurves():
	m_key_point_index_on_move(-1)
{
}

AnimatedCurves::~AnimatedCurves()
{
}


bool AnimatedCurves::HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
{
	int xPos = GET_X_LPARAM(lParam); 
	int yPos = GET_Y_LPARAM(lParam); 
	cout << xPos << "," << yPos;
	xPos = xPos - m_screen_width/2;
	yPos = m_screen_height/2 - yPos;

	switch( msg )
	{
	case WM_LBUTTONDOWN:
		if ( !m_current_streak->m_showCurve )
			break;

		if ( wParam & MK_CONTROL )
		{
			// Remove key points 
			int i = m_current_streak->FindKeyPointAt( xPos, yPos );
			if ( i != -1 )
				m_current_streak->RemoveKeyPoint( i );
		}
		else if ( wParam & MK_SHIFT )
		{
			// Move key points
			m_key_point_index_on_move = m_current_streak->FindKeyPointAt( xPos, yPos );
		}
		else
		{
			// Add key points
			m_current_streak->AddKeyPoint(xPos,yPos);
		}

		break;

	case WM_LBUTTONUP:
		m_key_point_index_on_move = -1;
		m_current_streak->ReCalc();
		break;

	case WM_MOUSEMOVE:
		if ( m_key_point_index_on_move != -1 )
		{
			m_current_streak->MoveKeyPoint( m_key_point_index_on_move, xPos, yPos );
			m_current_streak->ReCalc();
		}
		break;

	case WM_KEYDOWN:
		switch( wParam )
		{	
			case VK_SPACE:
				Reset();
				break;
			case 'N':
 				InitNewStreak();
				break;
			case 'C':
				for( size_t i=0; i < m_streaks.size(); i++ )
					m_streaks[i].m_showCurve = !m_streaks[m_streaks.size()-1].m_showCurve;
				break;
			case 'W':
				for( size_t i=0; i < m_streaks.size(); i++ )
					m_streaks[i].ToggleWireFrame();
				break;
			case 'S':
				{
					Image img;
					img.LoadViewport();
					img.Write("viewport.png", IMAGE_PNG);
				}
				break;
			default:
				break;
		}
	default:
		if ( Scene::HandleMessage( msg, wParam, lParam ) == false )
			return false;
	};

	return true;
}

bool AnimatedCurves::Init()
{
	Scene::Init();

	GetScreenDimension(m_screen_width, m_screen_height);
	int width = m_screen_width;
	int height = m_screen_height;

	// Setup render specific stuff
	{
		float	fov				= 60.f;
		float	nearZ			= 0.1f;
		float	farZ			= 1024.f;

		float	cameraPos[]		= { 0.f, 0.f, 100.f };
		float	lightPos[]		= { 0.f, 50.f, 50.f, 1.f };
		float	lightAmbient[]	= { 0.1f, 0.1f, 0.1f, 1.0f };
		float	lightDiffuse[]	= { 0.6f, 0.6f, 0.6f, 1.0f };


		// Setup viewport and projection matrix
		glViewport( 0, 0, width, height );

		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		//gluPerspective( fov, (float)width/(float)height, nearZ, farZ );
		glOrtho(-width/2, width/2, -height/2, height/2, nearZ, farZ);

		// Setup view matrix
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity();
		gluLookAt( cameraPos[0], cameraPos[1], cameraPos[2],	// Camera position
				   0.f, 0.f, 0.f,		// LookAt
				   0.f, 1.f, 0.f );		// Up
	

		// Other OpenGL flags
		glEnable(GL_DEPTH_TEST);				// Depthtest
		glEnable(GL_CULL_FACE);					// Cull face
		glEnable(GL_BLEND);						// Blending
		//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glBlendFunc(GL_ONE, GL_ONE);
		glShadeModel(GL_SMOOTH);				// Shading

		// Setup lighting
		//glEnable(GL_LIGHTING);
		glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
		glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
		glEnable(GL_LIGHT0);

		// Setup material
		//glEnable(GL_COLOR_MATERIAL);
		//glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	}



	//m_texture.Load( "texture.bmp", IMAGE_BMP );
	m_texture.Load( "texture.png", IMAGE_PNG );

	mOprMode = PLOT_CURVE;
	InitNewStreak();

	// Hud Text
	m_HUD_text.push_back( "Keys" );
	m_HUD_text.push_back( "SPACE: Clear Scene" );
	m_HUD_text.push_back( "N: New Curve" );
	m_HUD_text.push_back( "W: Toggle WireFrame" );
	m_HUD_text.push_back( "C: Toggle Path" );
	m_HUD_text.push_back( "" );
	m_HUD_text.push_back( "Mouse" );
	m_HUD_text.push_back( "Left: Plot keypoints" );
	m_HUD_text.push_back( "Left+Shift: Move keypoints" );
	m_HUD_text.push_back( "Left+Ctrl: Delete keypoints" );

	return true;
}

void AnimatedCurves::UnInit()
{
	Reset();
	Scene::UnInit();
}

void AnimatedCurves::Reset()
{
	for( size_t i=0; i < m_streaks.size(); i++ )
		m_streaks[i].Reset();
	m_streaks.clear();
}

void AnimatedCurves::Update()
{
	Scene::Update();

	const int MAX_DELAY = 1;
	static int delay = MAX_DELAY;
	//if ( mOprMode == ANIMATE )
	{
		delay--;
		if ( delay < 0 )
		{
			delay = MAX_DELAY;
			for( size_t i=0; i < m_streaks.size(); i++ )
				m_streaks[i].ReconstructMeshStrip();
		}
	}
}

void AnimatedCurves::Render( Renderer* renderer )
{
	// Clear back buffer
	glClearColor( 0.0, 0.0, 0.0, 0.0 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;

	// Render streaks
	glEnable(GL_POLYGON_OFFSET_FILL);
	for( size_t i=0; i < m_streaks.size(); i++ )
	{
		glPolygonOffset(1,m_streaks.size()-i);
		m_streaks[i].Render( renderer );
	}

	RenderHUD( renderer, 0.6, 0.85, m_HUD_text );
}

void AnimatedCurves::SetScreenDimension( int w, int h )
{
	m_screen_width = w;
	m_screen_height = h;
}

void AnimatedCurves::InitNewStreak()
{
	m_streaks.push_back( Streak() );
	m_current_streak = &m_streaks.back();

	m_current_streak->m_texture = &m_texture;
	m_current_streak->m_mesh.m_visible = true;
	m_current_streak->Init();
}



Streak::Streak():
	m_showCurve(true),
	m_num_bezier_segments(10),
	m_mesh_length_in_segments(8),
	m_mesh_width(100),
	m_mesh_length(300),
	m_speed(1),
	m_fade_in_frames(1),
	m_fade_out_frames(1)
{
}

void Streak::AddKeyPoint( int x, int y )
{
	m_key_points.push_back( Vector2(x,y) );
}

void Streak::MoveKeyPoint( int i, int x, int y )
{
	m_key_points[i].set( x, y );
}

void Streak::RemoveKeyPoint( int i )
{
	m_key_points.erase( m_key_points.begin() + i );
}

int Streak::FindKeyPointAt( int x, int y )
{
	if ( m_key_points.size() == 0 )
		return -1;

	int tolerance = 4;
	for( int i=(int)m_key_points.size()-1; i >= 0; i-- )
		if ( abs(m_key_points[i].x - x) < tolerance  &&  abs(m_key_points[i].y - y) < tolerance )
			 return i;
		
	return -1;
}

void Streak::ReCalc()
{
	CalcCurve();

	// Recalculate mesh
	if ( m_mesh.m_visible )
	{
		m_mesh_location_on_curve = 0;
		ReconstructMeshStrip();
	}
}

void Streak::CalcCurve()
{
	BezierCurve( m_key_points, m_num_bezier_segments, m_curve_points );
/*
	m_dividing_points.clear();
	m_tangent_points.clear();
	m_curve_points.clear();

	if ( m_key_points.size() < 3 )
		return;


	// Find the dividing vector for adjacent key points and get the tangent points
	m_tangent_points.push_back(  m_key_points[0] + (m_key_points[1] - m_key_points[0])/2.f  );
	
	Vector2 dv, tv, kl1, kl2;
	for( size_t i=1; i < m_key_points.size()-1; i++ )
	{
		kl1 = (m_key_points[i-1] - m_key_points[i]).normalize();
		kl2 = (m_key_points[i+1] - m_key_points[i]).normalize();

		// Choose tangent length
		float tl1 = (m_key_points[i-1] - m_key_points[i]).norm();
		float tl2 = (m_key_points[i+1] - m_key_points[i]).norm();
		float tl = ( tl1 < tl2 ? tl1 : tl2 ) / 2.0f;


		dv = kl1 + kl2;
		m_dividing_points.push_back( m_key_points[i]+dv*tl );


		tv.x = dv.x * cos(PI/2) - dv.y * sin(PI/2);
		tv.y = dv.x * sin(PI/2) + dv.y * cos(PI/2);
		tv = tv.normalize() * tl;

		// Choose tangent side
		if ( tv * kl1 > tv * kl2)
		{
			m_tangent_points.push_back( m_key_points[i]+tv );
			m_tangent_points.push_back( m_key_points[i]-tv );
		}
		else
		{
			m_tangent_points.push_back( m_key_points[i]-tv );
			m_tangent_points.push_back( m_key_points[i]+tv );
		}
	}

	int i = m_key_points.size()-2;
	m_tangent_points.push_back(  m_key_points[i] + (m_key_points[i+1] - m_key_points[i])/2.f  );

	// Calculate curve points
	for( size_t i=0; i < m_key_points.size()-1; i++ )
	{
		BezierCurve( m_key_points[i], m_tangent_points[2*i], m_tangent_points[2*i+1], m_key_points[i+1], m_num_bezier_segments, m_curve_points );
		if ( i < m_key_points.size()-2 )
			m_curve_points.pop_back();
	}
*/
}

void Streak::Reset()
{
	m_key_points.clear();
	m_curve_points.clear();
	m_dividing_points.clear();
	m_tangent_points.clear();

	m_mesh.Reset();
}


void Streak::Init()
{
	// Mesh strip structure
	//	1 - 3 - 5 - 7 
	//	| / | / | / |
	//	0 - 2 - 4 - 6 ......

	float minTexHt = 0;
	float maxTexHt = 1;

	m_mesh.Reset();
	m_mesh.m_polygon_mode = Mesh::POLYGON_MODE_QUAD;
	m_mesh.m_render_method = Mesh::RENDER_METHOD_VERTEX_ARRAYS;

	for( int i=0; i < m_mesh_length_in_segments; i++ )
	{
		// Set polygon
		int j = i*2;
		m_mesh.m_face_vtx_indices.push_back( j );
		m_mesh.m_face_vtx_indices.push_back( j+2 );
		m_mesh.m_face_vtx_indices.push_back( j+3 );
		m_mesh.m_face_vtx_indices.push_back( j+1 );

		// Set tex coords
		m_mesh.m_vtx_texcoords.push_back( Vector2( (float)i/m_mesh_length_in_segments, minTexHt) );
		m_mesh.m_vtx_texcoords.push_back( Vector2( (float)i/m_mesh_length_in_segments, maxTexHt) );
	}
	m_mesh.m_vtx_texcoords.push_back( Vector2( 1.0f, minTexHt) );
	m_mesh.m_vtx_texcoords.push_back( Vector2( 1.0f, maxTexHt) );

	m_mesh.m_vtx_positions.resize( m_mesh.m_vtx_texcoords.size() );
}

// Calc number of mesh length in segments based on required mesh length
void Streak::CalcMeshLengthInSegments()
{
	// Calc number of mesh length in segments based on required mesh length
	m_mesh_length_in_segments = 0;
	float currStripLength = 0;
	vector<Vector2>::iterator curvePt = m_curve_points.begin() + m_mesh_location_on_curve;
	while ( currStripLength < m_mesh_length )
	{
		if ( (curvePt+1) == m_curve_points.end() )
			break;
		else
			// Middle point
			currStripLength += (*(curvePt+1) - *curvePt).norm();

		curvePt++;
		m_mesh_length_in_segments++;
	}
}

// Construct mesh strip
void Streak::ReconstructMeshStrip()
{
	if ( m_curve_points.size() == 0 )
		return;

	// Calc number of mesh length in segments based on required mesh length
	int prevMeshLengthInSegments = m_mesh_length_in_segments;
	CalcMeshLengthInSegments();


	// If not enough curve points, just reset
	if ( (int)m_curve_points.size() - m_mesh_location_on_curve < m_mesh_length_in_segments+2 )
	{
		m_mesh_location_on_curve = 0;
		return;
	}

	// Reconstruct mesh
	if ( prevMeshLengthInSegments != m_mesh_length_in_segments )
	{
		Init();
	}

	vector<Vector2>::iterator curvePt = m_curve_points.begin() + m_mesh_location_on_curve;

	// Get mesh point vectors (one point per line points that would be part of mesh)
	Vector2 pt, vec;
	for( int i=0; i< m_mesh_length_in_segments+1; i++, curvePt++ )
	{
		// 1st line point
		if ( curvePt == m_curve_points.begin() )
		{
			vec = (*(curvePt+1) - *curvePt).normalize();
			pt.set( vec.y, -vec.x );
		}
		// Last line point
		else if ( curvePt == m_curve_points.end() )
		{
			vec = (*curvePt - *(curvePt-1)).normalize();
			pt.set( vec.y, -vec.x );
		}
		// Middle point with positive height
		else
		{
			vec = (*(curvePt+1) - *curvePt).normalize();
			Vector2 adj = (*(curvePt-1) - *curvePt).normalize();
			pt = vec + adj;

			if ( pt.x == 0 && pt.y == 0 )
			{
				pt.set( vec.y, -vec.x );
			}
			else
			{
				pt = pt.normalize();
			}
		}

		// Make the mesh points oriented in one direction relative to the line
		if ( (pt % vec).z < 0 )
			pt = pt * -1;

		pt = pt * m_mesh_width / 2.0f;

		m_mesh.m_vtx_positions[i*2] = Vector3( *curvePt + pt );
		m_mesh.m_vtx_positions[i*2+1] = Vector3( *curvePt - pt );
	}


	// Advance mesh
	m_mesh_location_on_curve++;
}


void Streak::ToggleWireFrame()
{
	if ( m_mesh.m_visible )
	{
		m_mesh.m_render_mode = ( m_mesh.m_render_mode == Mesh::RENDER_MODE_DEFAULT ) ? Mesh::RENDER_MODE_WIREFRAME : Mesh::RENDER_MODE_DEFAULT;
	}
}


void Streak::Render( Renderer* renderer )
{
	if ( m_showCurve )
	{
		glDisable( GL_TEXTURE_2D );

		glColor3f( 0.8, 0.1, 0.0 );
		glPointSize( 4.0f );

		// Key points
		glBegin( GL_POINTS );
		for( size_t i=0; i < m_key_points.size(); i++ )
			glVertex2f( m_key_points[i].x, m_key_points[i].y );
		glEnd();

/*
		// Line between key points
		if ( m_key_points.size() > 1 )
		{
			glBegin( GL_LINE_STRIP );
			for( size_t i=0; i < m_key_points.size(); i++ )
				glVertex2f( m_key_points[i].x, m_key_points[i].y );
			glEnd();
		}

		// Dividing Vector
		glColor3f( 0.8, 0.1, 0.8 );
		glBegin( GL_LINES );
		for( size_t i=0; i < m_dividing_points.size(); i++ )
		{
			glVertex2f( m_key_points[i+1].x, m_key_points[i+1].y );
			glVertex2f( m_dividing_points[i].x, m_dividing_points[i].y );
	
			// 0:1,2  1:3,4   2:5,6   3:7,8   

			glVertex2f( m_key_points[i+1].x, m_key_points[i+1].y );
			glVertex2f( m_tangent_points[2*i+1].x, m_tangent_points[2*i+1].y );

			glVertex2f( m_key_points[i+1].x, m_key_points[i+1].y );
			glVertex2f( m_tangent_points[2*i+2].x, m_tangent_points[2*i+2].y );
		}
		glEnd();
*/

		// Curve: Line between interpolated points
		glColor3f( 0.8, 0.1, 0.8 );
		if ( m_curve_points.size() > 1 )
		{
			glBegin( GL_LINE_STRIP );
			for( size_t i=0; i < m_curve_points.size(); i++ )
				glVertex2f( m_curve_points[i].x, m_curve_points[i].y );
			glEnd();
		}
	}

	m_texture->Bind();
	glColor4f( 1, 1, 1, 0.1 );
	//glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
	m_mesh.Render();
}
